package org.fioma.tree;

import java.io.BufferedWriter;
import java.io.IOException;

public abstract class SimpleTreeHandler<L extends Label<L>, I extends InternalNode<L, I, F, N>, F extends LeafNode<L, I, F, N>, N extends Node<L, I, F, N>>
		implements TreeHandler<L, I, F, N> {

	// protected NF nodeFactory;
	//
	// public SimpleTreeHandler(NF nodeFactory) {
	// this.nodeFactory = nodeFactory;
	// }
	//
	@Override
	public I getOrCreateInternalNode(I node, L word) {
		if (word.getLength() == 0) {
			return node;
		} else {
			byte letterNextNode = word.getByte(0);
			N nextNode = node.nextNode(letterNextNode);
			int wordSize = word.getLength();
			int nextNodeLabelSize = nextNode.getLabel().getLength();
			if (nextNodeLabelSize == wordSize) {
				return (I) nextNode;
			} else {
				if (nextNodeLabelSize > wordSize) {
					return createChild(node, wordSize, letterNextNode);
				} else {
					return ((I) nextNode).getOrCreateInternalNode(word.substring(nextNodeLabelSize));
				}
			}
//			L nextNodeLabel = nextNode.getLabel();
//			int mismatch = nextNodeLabel.getMismatch(word);
//			if (mismatch == -1) {
//				return (I) nextNode;
//			} else {
//				if (mismatch < nextNodeLabel.getLength()) {
//					if (mismatch != word.getLength()) {
//						throw new RuntimeException("Internal node doesn't exist.");
//					} else {
//						return createChild(node, mismatch, letterNextNode);
//					}
//				} else {
//					return ((I) nextNode).getOrCreateInternalNode(word.substring(mismatch));
//				}
//			}
		}
	}

	@Override
	public I createChild(I node, int pos, byte letter) {
		N nextNode = node.nextNode(letter);
		L labelNextNode = nextNode.getLabel();
		I ret = createChildInternalNode(node, labelNextNode.substring(0, pos));
		nextNode.setLabel(labelNextNode.substring(pos));
		nextNode.setParent(ret);
		ret.setChild(nextNode, nextNode.getLabel().getByte(0));
		node.setChild((N) ret, letter);
		return ret;
	}

	@Override
	public I createChildInternalNode(I node, L label) {
		I ret = createInternalNode();
		ret.setLabel(label);
		ret.setParent(node);
		return ret;
	}

	@Override
	public F createChildLeaf(I node, L label) {
		F ret = createLeafNode();
		ret.setLabel(label);
		ret.setParent(node);
		return ret;
	}

	public abstract I createInternalNode();

	public abstract F createLeafNode();

	@Override
	public F addWord(I node, L word) {
		if (word != null && word.getLength() > 0) {
			byte firstLetter = word.getByte(0);
			N nextNode = node.nextNode(firstLetter);
			if (nextNode == null) {
				F ret = this.createChildLeaf(node, word);
				node.setChild((N) ret, firstLetter);
				return ret;
			} else {
				L nextWord = nextNode.getLabel();
				int mismatch = word.getMismatch(nextWord);
				if (mismatch >= 0 && mismatch < word.getLength()) {
					if (mismatch == nextWord.getLength()) {
						// next word will not be splitted and a new node will
						// not be created in this section
						if (nextNode.isLeaf()) {
							// only increase leaf label size
							nextNode.setLabel(word);
							return (F) nextNode;
						} else {
							return ((I) nextNode).addWord(word.substring(mismatch));
						}
					} else {
						// new internal node must be created
						I newInternalNode = this.createChild(node, mismatch, firstLetter);
						L nextLeafLabel = word.substring(mismatch);
						F ret = newInternalNode.createChildLeaf(nextLeafLabel);
						newInternalNode.setChild((N) ret, nextLeafLabel.getByte(0));
						return ret;
					}
				} else {
					// word already in the tree
					return null;
				}
			}
		} else {
			return null;
		}
	}

	@Override
	public void printWords(I node, BufferedWriter bw, String prefix) throws IOException {
		for (N nextNode : node.getChilds()) {
			if (nextNode != null) {
				if (nextNode.isLeaf()) {
					bw.write(prefix);
					bw.write(nextNode.getLabel().toString());
					bw.newLine();
				} else {
					((I) nextNode).printWords(bw, prefix + node.getLabel().toString());
				}
			}
		}

	}

	@Override
	public boolean find(I node, L word) {
		if (word == null || word.getLength() == 0) {
			return true;
		} else {
			byte firstLetter = word.getByte(0);
			N nextNode = node.nextNode(firstLetter);
			if (nextNode == null) {
				return false;
			} else {
				L nextWord = nextNode.getLabel();
				int mismatch = word.getMismatch(nextWord);
				if (mismatch >= 0 && mismatch < word.getLength()) {
					if (mismatch == nextWord.getLength()) {
						if (nextNode.isLeaf()) {
							return false;
						} else {
							return ((I) nextNode).find(word.substring(mismatch));
						}
					} else {
						return false;
					}
				} else {
					return true;
				}
			}
		}
	}

	// @Override
	// public NF getNodeFactory() {
	// return nodeFactory;
	// }
	//
	// @Override
	// public void setNodeFactory(NF nodeFactory) {
	// this.nodeFactory = nodeFactory;
	// }

}
